package com.stars.easyms.redis.interceptor;

import com.stars.easyms.redis.annotion.RedisHashGet;
import com.stars.easyms.redis.exception.RedisInterceptorException;
import com.stars.easyms.base.util.GenericTypeUtil;
import com.stars.easyms.base.util.ReflectUtil;
import org.aopalliance.intercept.MethodInvocation;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * <p>className: RedisHashGetInterceptor</p>
 * <p>description: RedisHashGet注解拦截器</p>
 *
 * @author guoguifang
 * @version 1.2.1
 * @date 2019-03-18 14:49
 */
public class RedisHashGetInterceptor extends AbstractRedisInterceptor<RedisHashGet> {

    @SuppressWarnings("unchecked")
    @Override
    protected Object invoke(MethodInvocation methodInvocation, RedisHashGet redisHashGet) throws RedisInterceptorException {
        Object result = null;
        if (isEnabled()) {
            Method pointMethod = methodInvocation.getMethod();
            Class<?> returnType = pointMethod.getReturnType();
            Type genericReturnType = pointMethod.getGenericReturnType();
            Class<?> returnMapKeyClass = GenericTypeUtil.getGenericClass(genericReturnType, 0);
            Set<String> hashKeySet = getRedisHashKey(methodInvocation);
            int hashKeySetSize = hashKeySet.size();
            boolean isInvalidReturnType = hashKeySetSize == 0
                    && Map.class.isAssignableFrom(returnType) && !String.class.equals(returnMapKeyClass);
            if (isInvalidReturnType) {
                logger.error("Method '{}' when have no redis hashKey and returnType is Map, the returnType should be Map<String, T>!",
                        ReflectUtil.getMethodFullName(pointMethod));
                return proceed(methodInvocation);
            }
            isInvalidReturnType = hashKeySetSize > 1
                    && (!Map.class.isAssignableFrom(returnType) || !String.class.equals(returnMapKeyClass));
            if (isInvalidReturnType) {
                logger.error("Method '{}' when have more than one redis field, returnType should be Map<String, T>!",
                        ReflectUtil.getMethodFullName(pointMethod));
                return proceed(methodInvocation);
            }

            String redisKey = getRedisKey(methodInvocation, redisHashGet.group());
            if (hashKeySetSize == 1) {
                String hashKey = (String) hashKeySet.toArray()[0];
                if (!(genericReturnType instanceof ParameterizedType)) {
                    result = easyMsRedisTemplate.hashGet(redisKey, hashKey, returnType);
                } else {
                    result = GenericTypeUtil.parseObject(easyMsRedisTemplate.hashGet(redisKey, hashKey), (ParameterizedType) genericReturnType, returnType);
                }
            } else if (Map.class.isAssignableFrom(returnType)) {
                ParameterizedType returnMapValueType = GenericTypeUtil.getGenericType(genericReturnType, 1);
                Class returnMapValueClass = GenericTypeUtil.getGenericClass(genericReturnType, 1);
                if (returnMapValueType == null) {
                    if (hashKeySetSize == 0) {
                        result = easyMsRedisTemplate.hashGetAll(redisKey, returnMapValueClass);
                    } else {
                        result = easyMsRedisTemplate.hashGet(redisKey, hashKeySet, returnMapValueClass);
                    }
                } else {
                    Map<String, String> redisMap;
                    if (hashKeySetSize == 0) {
                        redisMap = easyMsRedisTemplate.hashGetAll(redisKey);
                    } else {
                        redisMap = easyMsRedisTemplate.hashGet(redisKey, hashKeySet);
                    }
                    int redisMapSize = redisMap.size();
                    if (redisMapSize > 0) {
                        Map resultMap = new HashMap<>(redisMapSize);
                        redisMap.forEach((key, value) -> resultMap.put(key, GenericTypeUtil.parseObject(value, returnMapValueType, returnMapValueClass)));
                        result = resultMap;
                    }
                }
                if (result != null && hashKeySetSize > 0 && hashKeySetSize != ((Map) result).size()) {
                    result = null;
                }
            } else {
                result = easyMsRedisTemplate.hashGet(redisKey, returnType);
            }
            if (result != null && (!(result instanceof Map) || !((Map) result).isEmpty())) {
                return result;
            }
            result = proceed(methodInvocation);
            if (result != null) {
                if (hashKeySetSize == 1) {
                    easyMsRedisTemplate.hashSet(redisKey, (String) hashKeySet.toArray()[0], result);
                } else {
                    easyMsRedisTemplate.hashSet(redisKey, result);
                }
                // 若当前过期时间小于0则设置过期时间，否则不设置
                if (easyMsRedisTemplate.getExpire(redisKey, TimeUnit.MILLISECONDS) <= 0) {
                    Long expire = getExpire(methodInvocation, redisHashGet.expire());
                    if (expire != null && expire > 0) {
                        easyMsRedisTemplate.expire(redisKey, expire, TimeUnit.MILLISECONDS);
                    }
                }
            }
        } else {
            result = proceed(methodInvocation);
        }
        return result;
    }
}